import tkinter as tk
import copy
from tkinter import ttk, messagebox
import json
from datetime import datetime, timedelta
import os
from tkcalendar import DateEntry

class DomainManagerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("域名到期管理工具")
        self.root.geometry("800x600")
        self.root.resizable(True, True)

        # 设置中文字体
        self.style = ttk.Style()
        self.style.configure("Treeview.Heading", font=("SimHei", 10, "bold"))
        self.style.configure("Treeview", font=("SimHei", 10), rowheight=25)

        # 数据文件路径
        # 使用用户应用数据目录存储数据文件，确保可写权限
        data_dir = os.path.join(os.path.expanduser("~"), "AppData", "Roaming", "DomainManager")
        os.makedirs(data_dir, exist_ok=True)
        self.data_file = os.path.join(data_dir, "data.json")
        self.domains = self.load_data()
        # 初始化操作历史栈
        self.history = []
        # 保存初始状态
        self.save_state()

        # 创建UI
        self.create_widgets()
        # 延迟调用reset_search确保UI组件完全初始化
        self.root.after(0, self.reset_search)

    def open_add_dialog(self):
        """打开添加域名的弹窗"""
        dialog = tk.Toplevel(self.root)
        dialog.title("添加域名")
        dialog.geometry("400x220")
        # 确保弹窗在父窗口范围内居中
        parent_x = self.root.winfo_x()
        parent_y = self.root.winfo_y()
        parent_width = self.root.winfo_width()
        parent_height = self.root.winfo_height()
        dialog_width = 400
        dialog_height = 220
        dialog_x = parent_x + (parent_width - dialog_width) // 2
        dialog_y = parent_y + (parent_height - dialog_height) // 2
        dialog.geometry(f"400x220+{dialog_x}+{dialog_y}")
        dialog.resizable(False, False)
        dialog.configure(padx=20, pady=20)

        # 域名输入
        ttk.Label(dialog, text="域名:").grid(row=0, column=0, padx=5, pady=10, sticky=tk.W)
        domain_entry = ttk.Entry(dialog, width=30)
        domain_entry.grid(row=0, column=1, padx=5, pady=10)

        # 到期日期选择
        ttk.Label(dialog, text="到期日期:").grid(row=1, column=0, padx=5, pady=10, sticky=tk.W)
        date_entry = DateEntry(dialog, width=30, date_pattern='yyyy-mm-dd', locale='zh_CN')
        date_entry.grid(row=1, column=1, padx=5, pady=10)

        # 备注输入
        ttk.Label(dialog, text="备注:").grid(row=2, column=0, padx=5, pady=10, sticky=tk.W)
        notes_entry = ttk.Entry(dialog, width=30)
        notes_entry.grid(row=2, column=1, padx=5, pady=10)

        def save_new_domain():
            domain = domain_entry.get().strip()
            expiry_date = date_entry.get()
            notes = notes_entry.get().strip()

            if not domain:
                messagebox.showwarning(parent=self.root, title="警告", message="域名不能为空")
                return

            # 检查是否已存在相同域名
            for d in self.domains:
                if d["domain"] == domain:
                    messagebox.showwarning(parent=self.root, title="警告", message=f"域名 '{domain}' 已存在")
                    return

            self.domains.append({
                "domain": domain,
                "expiry_date": expiry_date,
                "notes": notes
            })

            self.save_state()
            self.update_domain_list()
            self.save_data()
            dialog.destroy()
            messagebox.showinfo(parent=self.root, title="成功", message="域名添加成功")

        # 按钮区域
        button_frame = ttk.Frame(dialog)
        button_frame.grid(row=3, column=0, columnspan=2, pady=15)
        ttk.Button(button_frame, text="确认", command=save_new_domain).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)

        # 设置弹窗为模态
        dialog.transient(self.root)
        dialog.grab_set()
        self.root.wait_window(dialog)

    def open_edit_dialog(self, domain_data):
        """打开编辑域名的弹窗"""
        dialog = tk.Toplevel(self.root)
        dialog.title("编辑域名")
        dialog.geometry("400x220")
        # 确保弹窗在父窗口范围内居中
        parent_x = self.root.winfo_x()
        parent_y = self.root.winfo_y()
        parent_width = self.root.winfo_width()
        parent_height = self.root.winfo_height()
        dialog_width = 400
        dialog_height = 220
        dialog_x = parent_x + (parent_width - dialog_width) // 2
        dialog_y = parent_y + (parent_height - dialog_height) // 2
        dialog.geometry(f"400x220+{dialog_x}+{dialog_y}")
        dialog.resizable(False, False)
        dialog.configure(padx=20, pady=20)

        # 域名输入（不可编辑）
        ttk.Label(dialog, text="域名:").grid(row=0, column=0, padx=5, pady=10, sticky=tk.W)
        domain_entry = ttk.Entry(dialog, width=30)
        domain_entry.insert(0, domain_data["domain"])
        domain_entry.config(state="readonly")
        domain_entry.grid(row=0, column=1, padx=5, pady=10)

        # 到期日期选择
        ttk.Label(dialog, text="到期日期:").grid(row=1, column=0, padx=5, pady=10, sticky=tk.W)
        date_entry = DateEntry(dialog, width=30, date_pattern='yyyy-mm-dd', locale='zh_CN')
        date_entry.set_date(domain_data["expiry_date"])
        date_entry.grid(row=1, column=1, padx=5, pady=10)

        # 备注输入
        ttk.Label(dialog, text="备注:").grid(row=2, column=0, padx=5, pady=10, sticky=tk.W)
        notes_entry = ttk.Entry(dialog, width=30)
        notes_entry.insert(0, domain_data["notes"])
        notes_entry.grid(row=2, column=1, padx=5, pady=10)

        def save_edited_domain():
            expiry_date = date_entry.get()
            notes = notes_entry.get().strip()

            # 更新域名数据
            for d in self.domains:
                if d["domain"] == domain_data["domain"]:
                    d["expiry_date"] = expiry_date
                    d["notes"] = notes
                    break

            self.save_state()
            self.update_domain_list()
            self.save_data()
            dialog.destroy()
            messagebox.showinfo(parent=self.root, title="成功", message="域名更新成功")

        # 按钮区域
        button_frame = ttk.Frame(dialog)
        button_frame.grid(row=3, column=0, columnspan=2, pady=15)
        ttk.Button(button_frame, text="确认", command=save_edited_domain).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)

        # 设置弹窗为模态
        dialog.transient(self.root)
        dialog.grab_set()
        self.root.wait_window(dialog)

    def on_double_click(self, event):
        """处理列表双击事件"""
        selected_items = self.domain_tree.selection()
        if not selected_items:
            return
        selected_item = selected_items[0]
        domain_name = self.domain_tree.item(selected_item, "values")[0]
        for domain in self.domains:
            if domain["domain"] == domain_name:
                self.open_edit_dialog(domain)
                break

    def load_data(self):
        """加载域名数据"""
        if os.path.exists(self.data_file):
            try:
                with open(self.data_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except (json.JSONDecodeError, Exception) as e:
                messagebox.showerror(parent=self.root, title="错误", message=f"加载数据失败: {str(e)}")
                return []
        return []

    def save_state(self):
        """保存当前状态到历史栈，用于撤销操作"""
        # 深拷贝当前域名列表状态
        import copy
        self.history.append(copy.deepcopy(self.domains))
        # 限制历史记录数量，避免内存占用过大
        if len(self.history) > 100:
            self.history.pop(0)

    def save_data(self):
        """保存域名数据到文件"""
        try:
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(self.domains, f, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror(parent=self.root, title="错误", message=f"保存数据失败: {str(e)}")
            return False
        return True

    def undo_last_operation(self):
        """撤销上一步操作"""
        if len(self.history) > 1:
            self.history.pop()  # 移除当前状态
            self.domains = copy.deepcopy(self.history[-1])  # 恢复上一状态
            self.update_domain_list()
            self.save_data()
            messagebox.showinfo(parent=self.root, title="成功", message="已撤销上一步操作")
            return True
        messagebox.showinfo(parent=self.root, title="提示", message="已到达最早状态，无法继续撤销")
        return False

    def create_widgets(self):
        """创建UI组件"""
        # 创建搜索区域
        search_frame = ttk.Frame(self.root)
        search_frame.pack(fill=tk.X, padx=10, pady=5)

        # 域名搜索
        ttk.Label(search_frame, text="域名:").pack(side=tk.LEFT, padx=5)
        self.domain_search = ttk.Entry(search_frame, width=15)
        self.domain_search.pack(side=tk.LEFT, padx=5)

        # 状态筛选
        ttk.Label(search_frame, text="状态:").pack(side=tk.LEFT, padx=5)
        self.status_var = tk.StringVar(value="全部")
        status_combo = ttk.Combobox(search_frame, textvariable=self.status_var, values=["全部", "正常", "即将到期", "已过期"], width=10, state="readonly")
        status_combo.pack(side=tk.LEFT, padx=5)


        ttk.Button(search_frame, text="新增", command=self.open_add_dialog).pack(side=tk.LEFT, padx=5)
        ttk.Button(search_frame, text="搜索", command=self.update_domain_list).pack(side=tk.LEFT, padx=5)
        ttk.Button(search_frame, text="重置", command=self.reset_search).pack(side=tk.LEFT, padx=5)
        ttk.Button(search_frame, text="删除选中", command=self.delete_domain).pack(side=tk.LEFT, padx=5)

        # 创建列表显示区域
        list_frame = ttk.LabelFrame(self.root, text="域名列表", padding=(10, 10))
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # 创建Treeview组件
        columns = ("domain", "expiry_date", "days_left", "status", "notes")
        self.domain_tree = ttk.Treeview(list_frame, columns=columns, show="headings")

        # 设置列宽和标题
        self.domain_tree.column("domain", width=150, anchor=tk.W)
        self.domain_tree.column("expiry_date", width=120, anchor=tk.CENTER)
        self.domain_tree.column("days_left", width=80, anchor=tk.CENTER)
        self.domain_tree.column("status", width=100, anchor=tk.CENTER)
        self.domain_tree.column("notes", width=300, anchor=tk.W)

        self.domain_tree.heading("domain", text="域名")
        self.domain_tree.heading("expiry_date", text="到期日期")
        self.domain_tree.heading("days_left", text="剩余天数")
        self.domain_tree.heading("status", text="状态")
        self.domain_tree.heading("notes", text="备注")

        # 添加滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.domain_tree.yview)
        self.domain_tree.configure(yscroll=scrollbar.set)

        self.domain_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 绑定双击事件用于编辑
        self.domain_tree.bind("<Double-1>", self.on_double_click)

        # 绑定撤销快捷键
        self.root.bind("<Control-z>", lambda event: self.undo_last_operation())
        self.root.bind("<Control-Z>", lambda event: self.undo_last_operation())

    def get_days_until_expiry(self, expiry_date_str):
        """计算距离到期的天数"""
        try:
            expiry_date = datetime.strptime(expiry_date_str, "%Y-%m-%d")
            today = datetime.today()
            delta = expiry_date - today
            return delta.days
        except ValueError:
            return -1

    def reset_search(self):
        """重置所有搜索条件"""
        self.domain_search.delete(0, tk.END)
        self.status_var.set("全部")
        # 
        self.update_domain_list()

    def filter_domains(self):
        """根据多条件筛选域名"""
        domain_keyword = self.domain_search.get().strip().lower()
        status_filter = self.status_var.get()

        # 所有筛选条件为空时直接返回全部数据
        if not domain_keyword and status_filter == "全部":
            return self.domains

        filtered = []
        for domain in self.domains:
            # 域名筛选
            if domain_keyword and domain_keyword not in domain["domain"].lower():
                continue

            # 状态筛选
            days_left = self.get_days_until_expiry(domain["expiry_date"])
            if days_left < 0:
                status = "已过期"
            elif days_left <= 30:
                status = "即将到期"
            else:
                status = "正常"

            if status_filter != "全部" and status != status_filter:
                continue

            filtered.append(domain)
        return filtered

    def update_domain_list(self):
        """更新域名列表显示，支持搜索筛选"""
        # 清空现有项
        for item in self.domain_tree.get_children():
            self.domain_tree.delete(item)

        # 筛选域名
        filtered_domains = self.filter_domains()

        # 按到期日期排序
        sorted_domains = sorted(filtered_domains, key=lambda x: x["expiry_date"])

        # 添加到列表
        for domain in sorted_domains:
            days_left = self.get_days_until_expiry(domain["expiry_date"])

            # 确定状态和颜色
            if days_left < 0:
                status = "已过期"
                tag = "expired"
            elif days_left <= 30:
                status = "即将到期"
                tag = "soon"
            else:
                status = "正常"
                tag = "normal"

            # 添加到Treeview
            self.domain_tree.insert("", tk.END, values=(
                domain["domain"],
                domain["expiry_date"],
                days_left if days_left >= 0 else "无效日期",
                status,
                domain["notes"]
            ), tags=(tag,))

        # 配置标签样式
        self.domain_tree.tag_configure("normal", foreground="black")
        self.domain_tree.tag_configure("soon", foreground="red", font=("SimHei", 10, "bold"))
        self.domain_tree.tag_configure("expired", foreground="gray", font=("SimHei", 10, "italic"))

    def validate_date(self, date_str):
        """验证日期格式是否正确"""
        try:
            datetime.strptime(date_str, "%Y-%m-%d")
            return True
        except ValueError:
            return False





    def delete_domain(self):
        """删除选中的域名"""
        selected_items = self.domain_tree.selection()
        if not selected_items:
            messagebox.showwarning(parent=self.root, title="警告", message="请先选择要删除的域名")
            return

        selected_item = selected_items[0]
        domain = self.domain_tree.item(selected_item, "values")[0]

        if messagebox.askyesno(parent=self.root, title="确认", message=f"确定要删除域名 '{domain}' 吗？"):
            self.domains = [d for d in self.domains if d["domain"] != domain]
            self.save_state()  # 保存操作前状态
            self.update_domain_list()
            self.save_data()  # 实时保存到文件
            messagebox.showinfo(parent=self.root, title="成功", message="域名已删除")

    def clear_entries(self):
        """清空输入框"""
        self.domain_entry.delete(0, tk.END)
        self.expiry_entry.delete(0, tk.END)
        self.notes_entry.delete(0, tk.END)

if __name__ == "__main__":
    root = tk.Tk()
    app = DomainManagerApp(root)
    root.mainloop()